#ifndef UTILS_H
#define UTILS_H

#include <algorithm>
#include <queue>
#include <thread>
#include <vector>
#include <future>
#include <QVariant>
#include <QString>
#include <QEventLoop>
#include "threadsafe_linkedqueue.h"
#include <QWidget>

#define APPNAME "HypespectralProcess"

template<typename E>
constexpr std::underlying_type_t<E> toUType(E enumerator) noexcept {
    return static_cast<std::underlying_type_t<E>>(enumerator);
}

template <typename T>
struct BufferArray final {
private:
    int windowSize = 0;  //700
    int pixels = 0;   // 500
    int bands = 0;  // 224
    T *dataArray = nullptr;
public:
    BufferArray(int _bands, int _pixels) : bands(_bands), pixels(_pixels) {
        dataArray = new T[_bands * _pixels];
    }

    T& operator[](std::size_t n) {
        return dataArray[n];
    }

    const T& operator[](std::size_t n) const {
        return dataArray[n];
    }

    ~BufferArray() {
        delete []dataArray;
    }

    BufferArray(BufferArray&& other) noexcept : dataArray(other.dataArray), windowSize(other.windowSize), pixels(other.pixels), bands(other.bands)
    {
        other.dataArray = nullptr;
    }

    BufferArray& operator=(BufferArray&& s)
    {
        std::swap(dataArray, s.dataArray);
        windowSize = s.windowSize;
        bands = s.bands;
        pixels = s.pixels;
    }
    BufferArray(const BufferArray&)=delete;
    BufferArray(BufferArray&)=delete;
    BufferArray& operator=(const BufferArray&)=delete;
};

class join_QWidget {
    QWidget& widget;
public:
    explicit join_QWidget(QWidget& widget_):
    widget(widget_)
    {
        widget_.setEnabled(false);
    }
    ~join_QWidget()
    {
        widget.setEnabled(true);
    }
};


class join_threads
{
    std::vector<std::thread>& threads;
public:
    explicit join_threads(std::vector<std::thread>& threads_):
        threads(threads_)
    {}
    ~join_threads()
    {
        for(unsigned long i=0;i<threads.size();++i)
        {
            if(threads[i].joinable())
                threads[i].join();
        }
    }
};
class join_thread
{
    std::thread& thread;
public:
    explicit join_thread(std::thread& thread_):
            thread(thread_)
    {}
    ~join_thread()
    {
        if(thread.joinable())
            thread.join();
    }
};

class ManualReset final {

    bool flag_;
    std::mutex protect_;
    std::condition_variable signal_;

public:
    explicit ManualReset(bool initState = false) : flag_(initState) {

    }

    ManualReset(const ManualReset&) = delete;
    ManualReset& operator=(const ManualReset&) = delete; // non-copyable

    void reset() {
        std::lock_guard<std::mutex> _(protect_);
        flag_ = false;
    }

    void set() {
        std::lock_guard<std::mutex> _(protect_);
        flag_ = true;
        signal_.notify_one();
    }

    bool waitOne() {
        std::unique_lock<std::mutex> lk(protect_);
        while( !flag_ ) // prevent spurious wakeups from doing harm
            signal_.wait(lk);
        return true;
    }

    bool waitOne(int timeMillSeconds) {
        if(timeMillSeconds < 0) return waitOne();
        const auto timeout = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeMillSeconds);
        std::unique_lock<std::mutex> lk(protect_);
        while( !flag_ ) // prevent spurious wakeups from doing harm
            if(signal_.wait_until(lk, timeout) == std::cv_status::timeout)
                break;
        return flag_;
    }
};


class AutoReset final {

    bool flag_;
    std::mutex protect_;
    std::condition_variable signal_;

public:
    explicit AutoReset(bool initState = false) : flag_(initState) {

    }

    AutoReset(const AutoReset&) = delete;
    AutoReset& operator=(const AutoReset&) = delete; // non-copyable

    void reset() {
        std::lock_guard<std::mutex> _(protect_);
        flag_ = false;
    }

    void set() {
        std::lock_guard<std::mutex> _(protect_);
        flag_ = true;
        signal_.notify_one();
    }

    bool waitOne() {
        std::unique_lock<std::mutex> lk(protect_);
        while( !flag_ ) // prevent spurious wakeups from doing harm
            signal_.wait(lk);
        flag_ = false; // waiting resets the flag
        return true;
    }

    bool waitOne(int timeMillSeconds) {
        if(timeMillSeconds < 0) return waitOne();
        const auto timeout = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeMillSeconds);
        std::unique_lock<std::mutex> lk(protect_);
        while( !flag_ ) // prevent spurious wakeups from doing harm
            if(signal_.wait_until(lk, timeout) == std::cv_status::timeout)
                break;
        if(flag_) {
            flag_ = false; // waiting resets the flag
            return true;
        }
        else return false;
    }
};

class function_wrapper
{
    struct impl_base {
        virtual void call()=0;
        virtual ~impl_base() = default;
    };




    template<typename F>
    struct impl_type: impl_base
    {
        F f;
        explicit impl_type(F&& f_): f(std::move(f_)) {}
        void call() override { f(); }
    };

    std::unique_ptr<impl_base> impl;
public:
    template<typename F>
    explicit function_wrapper(F&& f):
        impl(new impl_type<F>(std::forward<F>(f)))
    {}
    void operator()() { impl->call(); }
    function_wrapper() = default;
    function_wrapper(function_wrapper&& other) noexcept :
        impl(std::move(other.impl))
    {}
    function_wrapper& operator=(function_wrapper&& other)
    {
        impl=std::move(other.impl);
        return *this;
    }
    function_wrapper(const function_wrapper&)=delete;
    function_wrapper(function_wrapper&)=delete;
    function_wrapper& operator=(const function_wrapper&)=delete;
};


class work_stealing_queue
{
private:
    typedef function_wrapper data_type;
    std::deque<data_type> the_queue;
    mutable std::mutex the_mutex;
public:
    work_stealing_queue() = default;
    work_stealing_queue(const work_stealing_queue& other)=delete;
    work_stealing_queue& operator=(
        const work_stealing_queue& other)=delete;
    void push(data_type data)
    {
        std::lock_guard<std::mutex> lock(the_mutex);
        the_queue.push_front(std::move(data));
    }
    bool empty() const
    {
        std::lock_guard<std::mutex> lock(the_mutex);
        return the_queue.empty();
    }
    bool try_pop(data_type& res)
    {
        std::lock_guard<std::mutex> lock(the_mutex);
        if(the_queue.empty())
        {
            return false;
        }
        res=std::move(the_queue.front());
        the_queue.pop_front();
        return true;
    }
    bool try_steal(data_type& res)
    {
        std::lock_guard<std::mutex> lock(the_mutex);
        if(the_queue.empty())
        {
            return false;
        }
        res=std::move(the_queue.back());
        the_queue.pop_back();
        return true;
    }
};


class thread_pool {
    typedef function_wrapper task_type;
    std::atomic_bool done;
    threadsafe_linkedqueue<function_wrapper, std::condition_variable> pool_work_queue;
    std::vector<std::unique_ptr<work_stealing_queue>> queues;
    std::vector<std::thread> threads;
    join_threads joiner;

    static thread_local work_stealing_queue *local_work_queue;
    static thread_local unsigned my_index;

    void worker_thread(unsigned my_index_) {
        my_index = my_index_;
        local_work_queue = queues[my_index].get();
        while(!done) {
            run_pending_task();
        }
    }

    bool pop_task_from_local_queue(task_type &task) {
        return local_work_queue && local_work_queue->try_pop(task);
    }

    bool pop_task_from_pool_queue(task_type &task) {
        return pool_work_queue.try_pop(task);
    }

    bool pop_task_from_other_thread_queue(task_type &task) {
        for(unsigned i = 0; i < queues.size(); ++i) {
            const unsigned index = (my_index + i + 1) % queues.size();
            if(queues[index]->try_steal(task)) {
                return true;
            }
        }
        return false;
    }

public:
    thread_pool() : done(false), joiner(threads) {
        unsigned const thread_count = std::thread::hardware_concurrency();
        try {
            for(unsigned i = 0; i < thread_count; ++i) {
                queues.push_back(std::unique_ptr<work_stealing_queue>(new work_stealing_queue));
            }

            for(unsigned i = 0; i < thread_count; ++i) {
                threads.push_back(std::thread(&thread_pool::worker_thread, this, i));
            }
        }
        catch(...) {
            done = true;
            throw;
        }
    }

    ~thread_pool() {
        done = true;
    }


public:
    template<typename FunctionType>
    std::future<typename std::result_of<FunctionType()>::type>
        submit(FunctionType f)
    {
        typedef typename std::result_of<FunctionType()>::type result_type;
        std::packaged_task<result_type()> task(std::move(f));
        std::future<result_type> res(task.get_future());
        if(local_work_queue) {
            local_work_queue->push(function_wrapper(std::move(task)));
        }
        else {
            pool_work_queue.push(function_wrapper(std::move(task)));
        }
        return res;
    }

    void run_pending_task() {
        task_type task;
        if(pop_task_from_local_queue(task) || pop_task_from_pool_queue(task) || pop_task_from_other_thread_queue(task)) {
            task();
        }
        else {
            std::this_thread::yield();
        }
    }

};


const int LoopSuccess = 0;
const int LoopTimeout = -1;
const int LoopError = -2;

class QLabel;
class QWidget;

namespace util {
    /*! @brief 从配置文件中读取指定配置 */
    QVariant getSetting(const QString& key, const QVariant& defaultValue = QVariant(), const QString& filename = "");
    /*! @brief 修改配置 */
    void setSetting(const QString& key, const QVariant& value, const QString& filename = "");

    /*! @brief 当前线程开启事件循环等待msecs毫秒, 返回QEventLoop执行结果 */
    int esleep(QEventLoop* loop, int msecs);

    /*! @brief 使程序唯一存在，如果已存在返回false */
    bool setProgramUnique(const QString& name);

    /*! @brief 检查完整文件路径，返回合理的完整文件路径。如果所属路径不存在则创建。isCover表示是否删除已有 */
    QString checkFile(const QString& filepath, bool isCover = false);

    /*! @brief 循环删除目录. 无法删除正在使用的文件和目录 */
    bool rmDir(const QString& path);

    qint64 mSecsSinceEpoch();

    QString localIpv4();

    QMap<QString, QString> localIpv4Map();

    bool ensureDirExist(const QString& dirPath);

    // app版本号
    QString appVersion();

    int screenWidth();
    int screenHeight();

    int scaleWidthByResolution(int width);
    QSize newSize(QSize size);

    void resetMaxWidth(QLabel* label, int maxWidth);

    // QLabel显示文本，超过最大宽度则显示省略号
    void setTextWithEllipsis(QLabel* label, const QString& text);

    QString logDir();

    // 是否有英伟达显卡
    bool haveNvidiaGpu();

    QString formatTime(int secs, const QString& format);
    QStringList secToTime(int secs);
    QString timestamp2String(qint64 ms);

    QString QVariant2QString(const QVariant& map);
    QVariant QString2QVariant(const QString& s);

    QString logRtspUrl(const QString& url);
    QString logRtspError(const QString& url);

    bool isDriveExist(const QString& drive);

    bool isFileExist(const QString& path);

    QStringList filePathListInDir(const QString& dirPath, QStringList filter = QStringList());

    QString appName();

    void setRetainSizeWhenHidden(QWidget* w, bool retain = true);
};


#endif // UTILS_H
